﻿using System;
using System.Web.Mvc;

namespace CodeContrib.Web.Mvc
{
	public class DateAndTimeModelBinder : IModelBinder
	{
		public string DateKey { get; set; }
		public string DayKey { get; set; }
		public string HourKey { get; set; }

		private bool IsDateSet
		{
			get { return !string.IsNullOrEmpty(DateKey); }
		}

		private bool IsDayMonthYearSet
		{
			get { return string.IsNullOrEmpty(DayKey) && !(string.IsNullOrEmpty(MonthKey) && string.IsNullOrEmpty(YearKey)); }
		}

		private bool IsHourMinuteSecondSet
		{
			get { return !(string.IsNullOrEmpty(HourKey) && string.IsNullOrEmpty(MinuteKey) && string.IsNullOrEmpty(SecondKey)); }
		}

		private bool IsTimeSet
		{
			get { return !string.IsNullOrEmpty(TimeKey); }
		}

		public string MinuteKey { get; set; }
		public string MonthKey { get; set; }
		public string SecondKey { get; set; }
		public string TimeKey { get; set; }
		public string YearKey { get; set; }

		public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
		{
			Guard.NotNull(bindingContext, "bindingContext");

			// Maybe we're lucky and they just want a DateTime the regular way.
			var dateTime = bindingContext.GetValueOrDefault<DateTime?>("DateTime");

			if (dateTime != null)
			{
				return dateTime.Value;
			}

			// If they haven't set Month,Day,Year OR Date, set "date" and get ready for an attempt.
			if (!IsDayMonthYearSet
			    && !IsDateSet)
			{
				DateKey = "Date";
			}

			// If they haven't set Hour, Minute, Second OR Time, set "time" and get ready for an attempt.
			if (!IsHourMinuteSecondSet
			    && !IsTimeSet)
			{
				TimeKey = "Time";
			}

			// Did they want the Date and Time?
			var date = bindingContext.GetValueOrDefault<DateTime?>(DateKey);
			var time = bindingContext.GetValueOrDefault<DateTime?>(TimeKey);

			// Maybe they wanted the Date via parts.
			if (IsDayMonthYearSet)
			{
				var year = bindingContext.GetValueOrDefault(YearKey, DateTime.MinValue.Year);
				var month = bindingContext.GetValueOrDefault(MonthKey, DateTime.MinValue.Month);
				var day = bindingContext.GetValueOrDefault(DayKey, DateTime.MinValue.Day);

				if (!year.IsValidYear())
				{
					bindingContext.ModelState.AddModelError(YearKey, string.Format("'{0}' is not a valid year.", year));
				}

				if (!month.IsValidMonth())
				{
					bindingContext.ModelState.AddModelError(MonthKey, string.Format("'{0}' is not a valid month.", month));
				}

				if (bindingContext.ModelState.IsValid
				    && !day.IsValidDay(year, month))
				{
					bindingContext.ModelState.AddModelError(DayKey, string.Format("'{0}' is not a valid day.", day));
				}

				if (bindingContext.ModelState.IsValid)
				{
					date = new DateTime(year, month, day, DateTime.MinValue.Hour, DateTime.MinValue.Minute, DateTime.MinValue.Second);
				}
			}

			// Maybe they wanted the Time via parts.
			if (IsHourMinuteSecondSet)
			{
				var hour = bindingContext.GetValueOrDefault(HourKey, DateTime.MinValue.Hour);
				var minute = bindingContext.GetValueOrDefault(MinuteKey, DateTime.MinValue.Minute);
				var second = bindingContext.GetValueOrDefault(SecondKey, DateTime.MinValue.Second);

				if (!hour.IsValidHour())
				{
					bindingContext.ModelState.AddModelError(HourKey, string.Format("'{0}' is not a valid hour.", hour));
				}

				if (!minute.IsValidMinute())
				{
					bindingContext.ModelState.AddModelError(MonthKey, string.Format("'{0}' is not a valid minute.", minute));
				}

				if (!second.IsValidSecond())
				{
					bindingContext.ModelState.AddModelError(DayKey, string.Format("'{0}' is not a valid second.", second));
				}

				if (bindingContext.ModelState.IsValid)
				{
					time = new DateTime(DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day, hour, minute, second);
				}
			}

			// If we got both parts, assemble them.
			return (date != null && time != null) ? new DateTime(date.Value.Year, date.Value.Month, date.Value.Day, time.Value.Hour, time.Value.Minute, time.Value.Second) : (date != null) ? date.Value : (time != null) ? time.Value : default(DateTime);
		}
	}
}